OnWeight is an iOS app that maps a combat athlete's weight cut from camp start to the official weigh-in — keeping the cut safe (gradual, power-preserving) and reading the scale honestly (a smoothed trend with cycle-aware adjustments, never the raw daily number). This document is the complete brief for building it: every screen, the logic behind them, the design system, the scope split, and where each area stands.
A fighter weighs in every morning. The numbers are noisy — water, food, glycogen — and judging a cut on a single reading is both wrong and stressful. OnWeight's whole job is to interpret those weigh-ins: smooth them into a true trend, project that trend to weigh-in day, and tell the athlete in plain, corner-coach language whether they'll make weight safely and on time.
The intelligence is three layers, and everything else hangs off them:
Smooth noisy daily weigh-ins into a true trajectory (an EWMA). The trend — not the raw scale — is what the athlete sees as a verdict.
Extrapolate the trend to weigh-in day, adjusted for the cycle-water the scale will read that morning. Drawn as a widening cone, not a false single line.
Classify pace — safe / too fast / behind / off-track — and coach accordingly. Red is reserved for genuine risk.
The app is paywalled end-to-end — no free tier. First-run is one funnel with a single hinge, the paywall: before it, the job is to convert (prove value, earn the subscription); after it, to activate (get the first weigh-in logged fast). The product then lives in a daily loop, escalates into a fight-week lifecycle, and falls back to a no-deadline maintenance mode between camps.
This is a health product for a RED-S-sensitive population. Weight-category athletes carry elevated risk of low energy availability. The design never suggests a faster cut, never shames a stall, treats a lost period as a wellness signal (not a convenience for the model), and caps everything against a 1%-of-bodyweight-per-week safety ceiling. Honour that posture in every build decision — it is the spine of the product, not a disclaimer.
The bundled files are design references, not production code. They are prototypes built in HTML + React-in-the-browser (Babel) that show the intended look, copy and behaviour exactly. Your task is to recreate these designs natively using the target codebase's patterns — not to port the React or ship the HTML. Re-implement idiomatically; treat the prototype as a faithful spec of layout, tokens, copy and flow.
Every screen and state on one pan/zoom canvas, grouped by flow. Click any artboard to focus it. This is the complete visual spec — open it to see exact pixels for anything below.
The wired, clickable build: real navigation, branching, live keypad and verdict logic. Open it and tap through; the jump menu lists every wired screen id.
The prototype is a screen registry plus a tap-delegation layer — the clearest spec of intended navigation. Two structures matter:
SCREENS — every screen, its type (flow = Next advances · tabbed = live tab bar · plain = back only) and its next/back targets.WIRE — per-screen rules [regex on the tapped element's text → destination]. Read this as the navigation table: it documents exactly where each control leads.Demo toggles in the prototype chrome — pace (on / behind / off), sport and sex — drive the verdicts, branching and the cycle model so you can reach every fork.
The full file list is in §20. A live screen browser is in §04.
The design is iOS-native in spirit. SwiftUI is the recommended target. Recreate the UI pixel-faithfully using native components rather than reproducing the prototype's bespoke chrome:
List / Form for grouped settings & rowsNavigationStack for push drill-downs + edge-swipe back.sheet with detents for scoped tasks (log weight, pickers, confirms, info sheets)Picker, Toggle, segmented controls for inputsColours, typography, spacing, copy and interactions are final. The two HTML artifacts are the pixel truth. Where this doc and a screen disagree, the screen wins — then flag it to design. Don't invent new colours or spacing; everything you need is in §11.
If a cross-platform stack is chosen instead (React Native, Flutter), the same fidelity bar applies — use that stack's idiomatic navigation, sheets and form controls. The prototype's React is a spec, never a dependency.
The product breaks into the flows below. Screen ids match the prototype jump menu and the live browser further down. Open All Screens for pixel detail on any of them.
splash → launch → sport → fight date (or no-fight) → biological sex → current weight → limit → cut rate → "building your camp" → Coach's Read → Plan Preview / paywall. The limit step gates to an out-of-range screen on an unsafe target. The Coach's Read shows the cycle card only for female athletes. Six high-signal questions; account & permissions deferred.
paywall → create account (Apple/Google + email, safety disclaimer, age gate) → dashboard empty state → log first weigh-in (the activation milestone). Precision setup is an opt-in "Sharpen your plan" card: weigh-in time → reminder → [female] cycle sub-flow → competition rules → cut strategies → check details → plan shape.
Today dashboard (hero trend numeral, pace verdict, the one red Add today's weight CTA, tracking card) → Log Weight (keypad) → Result (verdict from the typed number) → Trend (chart, recent log, weekly check-ins).
fight week → weigh-in day (keypad → Made/Missed by the number) → made/missed → rebuild → fight day → result → recovery; plus maintenance (no camp) and paused, with a Transition Confirm in/out.
Your Camp; Edit Plan — every field editable (division, weigh-in date, cut rate, gear allowance, rehydration cap), each opening an editor with a live "How this changes your plan" before→after panel; Sharpen accuracy inputs; Change Plan.
Menstrual Cycle (mode segmented, phase indicator, forecast, learned-from-your-data, setup rows), Cycle Check-in ("doesn't match how I feel" → phase-signs help + re-anchor). See §08.
single sport-avatar entry (top-right) → Account & units → Notifications, Units, Subscription (→ payment, billing), Privacy & data (→ export, legal); field editors; create account / verify / safety disclaimer / age gate (18+ only — under-18 not eligible).
offline, save-fail, declined-purchase, permission-denied, first-run empty states; subscription lifecycle (trial, grace, lapse, restore); notifications & sync; legal; imperial parity; localization (8 locales + RTL); widgets.
A curated set — the heroes of each flow. The complete set is in the live browser below and in All Screens.
Every screen, rendered live from the same components as All Screens. Pick one to load it in the device; scroll inside the device for the full screen.
Screens in the Future · sandbox group are reference-only and out of scope for R1 — see §16. They appear in this browser as a catalogue; they are never wired from a live screen.
Toggles flip, segmented controls and radios move, multi-selects toggle. Critically: anywhere copy says targets or the trend "adjust on the spot", the change must re-run the projection and reflect everywhere. The plan editors already preview a before→after; the build must persist and recompute.
prefers-reduced-motion. Entrance animations gate on the active state + reduced-motion so print/PDF/reduced-motion show content, never a pre-animation blank.| Field | Notes |
|---|---|
sport | One of 7. Drives rules, gear, coach voice, division ladder. |
biologicalSex | Selects the division ladder (divisions.men/women) and gates the cycle flow. |
hasFight + fightDate | No-fight users get an open-ended plan (maintenance, §10). |
currentWeight (kg) | Starting / walk-around weight. |
goalWeight (kg) | Fight-day scale target. User-facing word is "your limit" — the field name stays goalWeight; only the copy changed (see lexicon, §19). |
cutRate (%bw/wk) | 0.25 / 0.5 (recommended) / 0.75 / 1.0 (ceiling). |
cutStrategies[] | Fight-week tactics (low fibre, low sodium, sweat-out…). |
| competition rules | Per-sport, auto-filled, user-confirmable: equipment weight, re-weigh %, weigh-in timing, hydration clause. |
| cycle settings | mode + length + last-period (female + opted-in only). |
units | kg / lb. kg is canonical; convert at the edge (§11). |
| subscription state | trial / active / grace / lapsed. |
entries[]: { date, time, weight, note? } — one canonical fasted morning weigh-in per day; later same-day readings kept as context, never fed to the trend.// athlete-confirmed transitions — never silent
no-camp ⇄ camp ( active → fightWeek → weighIn → made|missed
→ rebuild → fightDay → result → recovery ) ⇄ paused
Editors are front-end-only in the prototype. In the build, persist every editor/picker selection, recompute the projection, and round-trip to the user record.
Full derivations and the evidence behind every default live in OnWeight - Logic & Flows. The essentials:
// 5-day exponentially-weighted moving average (the bold line; raw = faint dots) trend[i] = Σ ( weight[i-k] · 0.75^k ) / Σ ( 0.75^k ) for k = 0..4 weeklyVelocity = 7-day change in the trend // kg/week, negative = losing
scaleTarget = divisionLimit // the official class limit bodyTarget = scaleTarget − equipmentWeight // gear sports subtract gi/gear; underwear-only ≈ scaleTarget
projected = trendingToday + dailyVelocity × daysToWeighIn // true-weight weighInOffset = cycleOffsetKg(weighInDay) // kg the scale reads above true wt that day projectedScale = projected + weighInOffset // honest scale reading at weigh-in cycleBuffer = max(0, weighInOffset) // extra TRUE weight to pre-cut trueTarget = bodyTarget − cycleBuffer scaleGap = projectedScale − bodyTarget // the gap that decides made/missed
The Trend tab draws a projection cone — a widening fan whose upper edge carries the cycle-water risk — instead of an over-confident single line. Verdict: the cone clears / straddles / misses the limit.
| Option | Rate | Guidance |
|---|---|---|
| Maximum | 1.0%/wk | Hard safety ceiling — beyond this, muscle/strength loss is likely. |
| Aggressive | 0.75%/wk | Effective but may impact performance during camp. |
| Recommended | 0.5%/wk | Optimal — preserves muscle, supports performance. |
| Conservative | 0.25%/wk | Slow & steady; needs a longer timeline. |
| Tier | Method | Surfaced as | Fires re-plan? |
|---|---|---|---|
| Trend | EWMA | the weight shown | no |
| Drift-watch | CUSUM of deviation vs cycle line | quiet, non-red note | no (soft only) |
| Confirmed verdict | 7-day rolling rate + projection | verdict + Change-plan | yes |
Two independent re-plan triggers, both with hysteresis + a 2-reading confirm so the athlete is never bounced in and out of "off track": (A) projection miss — projected scale weight misses the limit across 2 readings; (B) safety ceiling — measured rate > 1%/wk regardless of whether weight will still be made.
A handful of threshold numbers (exact gauge boundaries) are evidence-based defaults flagged for sports-dietitian sign-off in Logic & Flows §6/§14. Implement them as configurable constants, not magic numbers.
Shown only when biologicalSex = female and the athlete opts in. The effect is small, fluid-only, and absent in many women — so the product leads with annotation over arithmetic.
cycleOffsetKg(day) = retention × −cos( 2π · (cycleDay − 10) / cycleLength ) // dips to −retention mid-follicular (~day 10), peaks +retention late luteal (~day 24) // default amplitude retention = 0.5 kg · user-adjustable 0–2.5 · contraception/absent = 0
Full phase model: water offset, undulating plan line, weigh-in buffer. Refined per-athlete from logged data.
No natural-phase model (HC suppresses the fluctuation). Default flat 0 offset; rely on logged symptoms.
No phase model. If "absent" is unexpected → the RED-S safeguard fires.
RED-S safeguard (non-negotiable). A lost period is a primary warning sign of low energy availability, not a convenience for the model. Detection: secondary amenorrhea = 3+ missed expected cycles; oligomenorrhea = cycle > 45 days. When detected: never treat as "0 offset, carry on" — surface a calm, non-diagnostic note, recommend a doctor check-in, and bias toward the conservative end (never suggest a faster cut). Wellness signal, never a diagnosis.
Learning (future-tier headline): once ~2–3 confirmed cycles are logged, fit each athlete's real luteal amplitude from the cyclic residual (detrend the cut first) and recommend an update — never silent, always overridable, labelled an estimate not medical advice.
The experience is bespoke per sport: weigh-in timing, equipment, re-weigh rule and the cut style all differ. Full sourced tables (men + women, every division) are in Logic & Flows §17 and cross-checked in Rules Verification. The shape:
| Sport | Weigh-in | Equipment | Re-weigh / hydration | Plan type |
|---|---|---|---|---|
| Boxing | Amateur same-day · pro day-before | none | — | amateur walk-it-down · pro camp |
| MMA | Day before (a.m.) | none | some orgs test hydration | camp (water cut viable) |
| Muay Thai | Varies — no universal standard | none | varies; ONE ≤5% regain | free-entry; same-day → walk-it-down |
| BJJ (IBJJF) | Same-day, in the gi, before you compete | gi (weighed dressed) | single weigh-in, no tolerance | minimal water cut |
| Taekwondo (WT) | Day before + random control | dobok | ≤ +5% of category limit | camp |
| Judo (IJF) | Day before + random control | judogi | ≤ +5% of category limit | camp |
| Wrestling (UWW) | Same-day, morning, in singlet | none | no tolerance | walk-it-down |
Weigh-in timing sets the max safe fight-week water cut: same-day → ~0% (water cutting disabled, "walk-it-down"); within ~2h (BJJ) → minimal; day-before → up to ~5% of body mass, bounded by the re-weigh cap. Ship a per-sport division table; the athlete picks a division (autofills the scale target) — the typed limit is the fallback. The published division limit is the scale limit; gear sports subtract equipmentWeight to get the body target.
With no fight date, the countdown engine has nothing to anchor to. Maintenance mode replaces it with two date-free signals:
Where today's trend sits in a maintenance band. The target is self-set but the app recommends a band (~8–15% over the class) and explains why, so the next cut isn't a crash. States: in band · drifting high · near the re-entry ceiling.
readinessWeeks = max(0, (currentTrend − bodyTarget) / (currentTrend × 1%)) — "from today, a safe cut to your class takes ~X weeks." The no-deadline analogue of the projection.
Anti-cycling is handled with gentle, educational nudges — never hard gates. Camp↔maintenance transitions are athlete-confirmed (prompted, flips only on confirmation) and carry over the walk-around baseline, division and learned-cycle data. Maintenance offers a "drop to weekly" weigh-in cadence.
Defined in fc-shared.jsx (THEMES.balanced). Full set + the cycle palette + WCAG figures are in Foundations. Every weight formats through a units engine (OWUnits) — never hard-code a unit string.
| Role | Family | Usage |
|---|---|---|
| Display | Oswald 500–700 | Eyebrows, buttons, scoreboard. Uppercase, letter-spacing 0.12–0.22em. |
| Body / titles | IBM Plex Sans | Large title 27/700; body 14.5–16 / 400–600. |
| Hero numerals | IBM Plex Sans 300 | Hero weights 64–112px, letter-spacing −0.03em. |
| Labels / metadata | IBM Plex Mono | 9.5–11px, uppercase, 0.06–0.16em. |
| Quotes | IBM Plex Serif italic | Fighter quotes on the proof band. |
--radius 20px (cards) · --radius-ctl 14px (controls/buttons)0 1px 3px rgba(20,18,12,.04), 0 8px 22px rgba(20,18,12,.05)Full states and anatomy are in Component Specs. The shared kit:
Phone · StatusBar · HomeIndicator · LargeTitle · CompactNav · TabBar · GearButton (the sport-avatar settings door).
Card · SectionHeader · GroupedList + Row (chevron only when navigable) · DashBanner · StepList.
Stat · ToneBadge · Toggle · HeroButton (the single red "Add today's weight" CTA) · CoachTip · AvatarRow · ProofBand.
Trend line + scrubber, target-zone corridor, projection cone, cut burndown, drift-watch, noise-vs-trend, cycle-adjusted plan — all theme/tone-aware (fc-chart.jsx, fc-instruments.jsx).
fc-engine.jsx — divisions (men + women), pace classifier, cycle model, social-proof data, per-sport rules.
OWUnits (fc-units.jsx) — kg↔lb conversion + formatting. kg is the source of truth; division tables stay canonical in their governing-body unit and convert for display.
From the Accessibility Pass on the Balanced theme. The palette is genuinely accessible — the warm paper costs nothing on contrast.
Primary ink #15140f on white = 18.4:1. Every meaning-carrying colour clears AA, most by a wide margin. One finding: rule-2 #8a7f68 is 3.95:1 — fine for chevrons/toggle tracks (non-text UI, AA needs 3:1) but never as text. The form-field placeholder was moved to ink-3 (8.4:1) — keep it there.
aria-hidden / no accessibility element.A Built for Mars-style critique scored each flow against 8 psychology principles. Most findings have been rolled into the prototype, designs and docs; what you implement should preserve the fixes and honour the two systemic notes. Full record in OnWeight - BFM Audit.
Every flow scores low on social proof in a sport that is intensely coach-and-gym social. The biggest untapped lever: an optional coach / peer visibility layer (kept in Future, §16).
The hard paywall (no free fallback) and the long gaps between camps make the reminder / win-back / "fight booked?" path core, not polish. Designed in the Future sandbox; wire it to a real lapse/win-back lifecycle.
Still open (business/content, not design): final trial length + price; annual-only vs annual+monthly; replacing labelled placeholders (proof fighters/quotes, win-back & widget pricing) with verified content.
Every feature, categorised, split first-release versus future. needs a call marks an open product decision (see §18). Filter to scan.
The launch-point rule (non-negotiable). Future-state screens live in the roadmap sandbox and are reachable only as a catalogue. A current/shipping screen must never wire a launch point into a not-yet-built future destination. Do not add nav into these from onboarding, settings, plan, or the dashboard.
These are designed as concepts (in fc-future.jsx) but are explicitly not in Release 1:
Conversational corner-coach. Needs scope, guardrails and a safety policy before design hardens.
Articles + video on cut science, the cycle, recovery. Layout exists; needs written + medically-reviewed content.
Meal & hydration guidance. Parked unless prioritised.
Smart-scale (Bluetooth) connect beyond Apple Health auto-import.
The re-engagement recovery the hard paywall depends on — design exists in sandbox; wiring to a real lapse lifecycle is a build task (see §14).
A coach's read-only roster of their fighters — the accountability / social layer. Athlete still owns the plan.
Also future-tier: weigh-in time-drift & week-vs-usual compare charts; cycle symptom logging, learned cycle model & forecast chart; all widgets.
OWUnits (kg↔lb) through every remaining screen; division tables canonical in source unit, convert at the edge, limits round down.The screen set is drafted across every journey. No area is "done" — everything is designed and awaiting review & approval. Treat sign-off as the minimum gate before build.
Onboarding & entry · Core loop · Lifecycle states · Accounts & safety · Subscription lifecycle · Legal & data rights · Imperial (lb) parity · Per-sport copy parity · Notifications & sync · Widgets · Errors, empty & localization.
| Decision | The call |
|---|---|
| Pricing & trial | Confirm price + trial length (figures in the prototype are placeholders; BFM suggests a longer trial converts better). Annual-only vs annual+monthly. Regional pricing. |
| Notification cadence | Sign off triggers + per-week limits in the content set (re-engagement cap). |
| Units roll-through | Eng task: wire the kg→lb engine through every remaining screen. |
| Gauge thresholds | Sports-dietitian sign-off on the exact pace boundaries (defaults set; §07). |
Learn library (needs written + medically-reviewed articles/video), Coach AI (needs scope + safety policy), Nutrition — all Future-tier (§16).
Future-state (Coach AI, Fuel, Devices & sync, Library) stays in the sandbox until built. No live screen hands off to a not-yet-built destination.
A single sport-avatar entry (top-right) → Account. Don't reintroduce competing gear/More/in-context settings entries.
The single red HeroButton ("Add today's weight"). Don't redraw it per screen.
Only show a forward chevron on rows that actually push a screen.
Cycle content is sex-gated. RED-S / lost-period messaging is supportive and non-diagnostic — never a faster-cut suggestion.
The athlete-facing word for the scale number is limit. Code/field names stay goalWeight / bodyTarget / scaleTarget; only display copy uses "limit". Follow Copy Rules §5.
?s=<id>); powers the live browser in §04.The .jsx files build the screens. Shared system is fc-shared.jsx; the engine is fc-engine.jsx; charts are fc-chart.jsx / fc-instruments.jsx; units are fc-units.jsx. The HTML files reference the JSX by filename and the JSX sits alongside them, so everything opens and runs as-is in a modern browser (in-browser Babel).
No external image assets. Every mark is inline SVG — the wordmark/app mark, the per-sport athlete logos (fc-logo-sports.jsx), sport pictograms (fc-icons.jsx) and UI icons (fc-ui-icons.jsx). Recreate these as a native icon set / asset catalog. Fonts: Oswald, IBM Plex Sans/Mono/Serif (+ Plex Sans Arabic/Hebrew/JP/KR for localization) — bundle equivalents.